package core.generator.jmlutils;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Iterator;

public class JMLSet<E> extends HashSet<E> {
	private static final long serialVersionUID = 4461664592234668152L;

	private Class<?> type;

	public/*@ pure @*/JMLSet(Class<?> type) {
		this.type = type;
	}

	public/*@ pure @*/JMLSet<E> select(JMLFilter<E> f) {
		JMLSet<E> filtrados = new JMLSet<E>(type);
		Iterator<E> it = iterator();
		while (it.hasNext()) {
			E valor = it.next();
			if (f.filtra(valor)) {
				filtrados.add(valor);
			}
		}
		return filtrados;
	}

	public/*@ pure @*/boolean forAll(JMLFilter<E> f) {
		return select(f).size() == size();
	}

	public/*@ pure @*/boolean includes(E o) {
		return contains(o);
	}

	public/*@ pure @*/E any(JMLFilter<E> f) {
		return select(f).iterator().next();
	}

	public/*@ pure @*/E at(int position) {
		Iterator<E> it = iterator();
		for (int i = 0; it.hasNext(); i++) {
			E next = it.next();
			if (position == i) {
				return next;
			}
		}
		return null;
	}

	public/*@ pure @*/JMLSet<E> reject(JMLFilter<E> f) {
		JMLSet<E> filtrados = new JMLSet<E>(type);
		Iterator<E> it = iterator();
		while (it.hasNext()) {
			E valor = it.next();
			if (!f.filtra(valor)) {
				filtrados.add(valor);
			}
		}
		return filtrados;
	}

	public/*@ pure @*/boolean notEmpty() {
		return !isEmpty();
	}

	public/*@ pure @*/int size() {
		return super.size();
	}

	public/*@ pure @*/boolean exists(JMLFilter<E> f) {
		return select(f).size() > 0;
	}

	public/*@ pure @*/boolean excludes(E o) {
		return !includes(o);
	}

	public/*@ pure @*/boolean includesAll(JMLSet<E> otherCollection) {
		return containsAll(otherCollection);
	}

	public/*@ pure @*/boolean excludesAll(JMLSet<E> otherCollection) {
		return !includesAll(otherCollection);
	}

	@SuppressWarnings("unchecked")
	public/*@ pure @*/<F> JMLSet<F> getAll(String attrName, Class<F> classe) {
		JMLSet<F> result = new JMLSet<F>(type);
		try {
			Iterator<E> it = iterator();
			Field field = type.getField(attrName);
			while (it.hasNext()) {
				Object next = it.next();
				result.add((F) field.get(next));
			}
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("O atributo '" + attrName
					+ "' da classe '" + type.getSimpleName()
					+ "' nao pode ser acessado!");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public/*@ pure @*/JMLSet<E> including(E obj){
		JMLSet<E> set = (JMLSet<E>) this.clone();
		set.add(obj);
		return set;
	}
	
	@SuppressWarnings("unchecked")
	public/*@ pure @*/JMLSet<E> excluding(E obj){
		JMLSet<E> set = (JMLSet<E>) this.clone();
		set.remove(obj);
		return set;
	}
	
	public/*@ pure @*/Integer count(E obj){
		int resp = 0;
		Iterator<E> it = iterator();
		while(it.hasNext()){
			E next = it.next();
			if(next.equals(obj))
				resp++;
		}
		return resp;
	}
	
	@SuppressWarnings("unchecked")
	public/*@ pure @*/JMLSet<E> union(JMLSet<E> other){
		JMLSet<E> clone = (JMLSet<E>) this.clone();
		clone.addAll(other);
		return clone;
	}
	
	@SuppressWarnings("unchecked")
	public/*@ pure @*/JMLSet<E> intersection(JMLSet<E> other){
		JMLSet<E> clone = (JMLSet<E>) this.clone();
		clone.retainAll(other);
		return clone;
	}
	
	public/*@ pure @*/Double sum() {
		double resp = 0;
		Iterator<E> it = iterator();
		while (it.hasNext()) {
			E next = it.next();
			if (!(next instanceof Double) || !(next instanceof Integer)) {
				throw new RuntimeException(
						"Impossivel a operacao sum para o tipo de objetos '"
								+ next.getClass().getSimpleName() + "'!");
			}
			resp += (Double) next;
		}
		return resp;
	}

	public/*@ pure @*/String toString() {
		return super.toString();
	}
}
